﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;

namespace LogoFX.Data
{
    public class EditableModel<T> : Model<T>, IEditableObject, ICanBeDirty where T : IEquatable<T>
    {
        readonly Stack<Snapshot> _editStack = new Stack<Snapshot>();
        private bool _isDirty;

        protected class Snapshot
        {
            private IList<ValidationResult> _validationErrors;
            private readonly IDictionary<PropertyInfo, object> _state = new Dictionary<PropertyInfo, object>();
            private readonly IDictionary<EventInfo, object> _eventState = new Dictionary<EventInfo, object>();
            private readonly IDictionary<PropertyInfo, IList<object>> _listsState = new Dictionary<PropertyInfo, IList<object>>();
            private bool _isDirty = false;

            public Snapshot(EditableModel<T> model)
            {

                foreach (PropertyInfo propertyInfo in model.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy))
                {
                    if (propertyInfo.IsDefined(typeof(EditableListAttribute), true) &&
                        typeof(IList).IsAssignableFrom(propertyInfo.GetValue(model, null).GetType()))
                    {
                        _listsState.Add(new KeyValuePair<PropertyInfo, IList<object>>(propertyInfo,
                            new List<object>(((IList)propertyInfo.GetValue(model, null)).OfType<object>())));
                    }
                    else if (propertyInfo.CanWrite && propertyInfo.CanRead && propertyInfo.GetSetMethod() != null)
                    {
                        _state.Add(new KeyValuePair<PropertyInfo, object>(propertyInfo,
                                                                          propertyInfo.GetValue(model, null)));
                    }
                }
                //foreach (EventInfo eventInfo in model.GetType().GetEvents(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy))
                //{
                //    _eventState.Add(new KeyValuePair<EventInfo, object>(eventInfo,
                //                                                      eventInfo.GetRaiseMethod(true),eventInfo.));                    
                //}
                _validationErrors = model.ValidationErrors;
                _isDirty = model.IsDirty;
            }

            public static Snapshot Take(EditableModel<T> model)
            {
                return new Snapshot(model);
            }

            public bool IsDirty
            {
                get { return _isDirty; }
            }

            public void Restore(EditableModel<T> model)
            {
                Type t = model.GetType();
                foreach (KeyValuePair<PropertyInfo, object> result in _state)
                {
                    if (result.Key.GetCustomAttributes(typeof(EditableSingleAttribute), true).Count() > 0 && result.Value is ICloneable<object>)
                    {
                        result.Key.SetValue(model, (result.Value as ICloneable<object>).Clone(), null);
                    }
                    else
                    {
                        result.Key.SetValue(model, result.Value, null);
                    }
                }
                //foreach (KeyValuePair<EventInfo, object> result in _eventState)
                //{
                //    result.Key.AddEventHandler(model, Delegate.CreateDelegate(result.Value);
                //}
                foreach (KeyValuePair<PropertyInfo, IList<object>> result in _listsState)
                {
                    IList il = (IList)result.Key.GetValue(model, null);
                    //todo:optimize this
                    il.Clear();
                    if (((EditableListAttribute)result.Key.GetCustomAttributes(typeof(EditableListAttribute), true)[0]).CloneItems)
                        result.Value.ForEach(a => il.Add(a is ICloneable<object> ? ((ICloneable<object>)a).Clone() : a));
                    else
                        result.Value.ForEach(a => il.Add(a));
                    //
                }
                model.ValidationErrors = _validationErrors;
#if SILVERLIGHT
                model.NotifyOfErrorsChanged(new DataErrorsChangedEventArgs(null));
#else
                model.NotifyOfPropertyChange(()=>model.Error);
#endif
                model.IsDirty = IsDirty;
            }
        }
        public void BeginEdit()
        {
            _editStack.Push(new Snapshot(this));
            NotifyOfPropertyChange(() => IsReadOnly);
            OnBeginEdit();
        }

        protected virtual void OnBeginEdit()
        { }

        public void EndEdit()
        {

            IsDirty = _editStack.Pop().IsDirty;
            NotifyOfPropertyChange(() => IsReadOnly);
            OnEndEdit();
        }

        protected virtual void OnEndEdit()
        { }

        public void CancelEdit()
        {
            OnCancelEdit();
            _editStack.Pop().Restore(this);
            NotifyOfPropertyChange(() => IsReadOnly);
        }

        protected virtual void OnCancelEdit()
        {

        }

        public override bool IsReadOnly
        {
            get
            {
                return _editStack.Count == 0;
            }
        }

        public virtual bool IsDirty
        {
            get
            {
                return _isDirty;
            }
            protected set
            {
                _isDirty = value; OnPropertyChanged(() => IsDirty);
            }
        }

        public virtual void ClearDirty()
        {
            IsDirty = false;
        }
    }
}
